package com.cxx.purchasecharge.component.bean;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.util.AntPathRequestMatcher;
import org.springframework.security.web.util.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import com.cxx.purchasecharge.component.bean.usersecurity.AuthorityBean;
import com.cxx.purchasecharge.component.exception.ApplicationException;
import com.cxx.purchasecharge.component.utils.ComponentConstants;
import com.cxx.purchasecharge.core.config.PurchaseChargeProperties;
import com.cxx.purchasecharge.core.model.RoleAuthority;
import com.cxx.purchasecharge.core.model.persistence.Authority;
import com.cxx.purchasecharge.core.model.persistence.Role;
import com.cxx.purchasecharge.core.model.persistence.User;
import com.cxx.purchasecharge.dal.AuthorityDao;
import com.cxx.purchasecharge.dal.RoleDao;
import com.cxx.purchasecharge.dal.UserDao;

public class PCApplicationContext implements Serializable, InitializingBean
{
    private static final long serialVersionUID = 1L;
    private static final Logger LOGGER = Logger.getLogger (PCApplicationContext.class);

    private LoginUser mockLoginUser;
    private Map <String, LoginUser> loginUserMap = new HashMap <String, LoginUser> ();
    private Map <RequestMatcher, Collection <ConfigAttribute>> securityResourceMap = new HashMap <RequestMatcher, Collection <ConfigAttribute>> ();
    
    private UserDao userDao;
    private RoleDao roleDao;
    private AuthorityDao authorityDao;
    private boolean initialized;
    private List<AuthorityBean> controllerRequestMappings;

    public LoginUser getLoginUser (String userId)
    {
        if (CollectionUtils.isEmpty (loginUserMap))
        {
            return null;
        }
        return loginUserMap.get (userId);
    }

    public LoginUser getLoginUser (HttpServletRequest request) throws Exception
    {
        LoginUser loginUser = null;
        if (null != request)
        {
            Object obj = request.getSession (false).getAttribute (ComponentConstants.LOGIN_USER);
            if (null != obj)
            {
                loginUser = (LoginUser) obj;
            }
            else
            {
                loginUser = getMockLoginUser ();
            }
        }
        if (null == loginUser)
        {
            LOGGER.warn ("Get Login User failed");
            throw new ApplicationException ("Get Login User failed");
        }
        return loginUser;
    }

    public void putLoginUser (LoginUser loginUser)
    {
        if (null != loginUser && StringUtils.isNotBlank (loginUser.getUserId ()))
        {
            loginUserMap.put (loginUser.getUserId (), loginUser);
        }
    }

    public LoginUser removeLoginUser (String userId)
    {
        if (StringUtils.isNotBlank (userId))
        {
            return loginUserMap.remove (userId);
        }
        return null;
    }

    public LoginUser getMockLoginUser ()
    {
        return mockLoginUser;
    }

    public void setMockLoginUser (LoginUser mockLoginUser)
    {
        this.mockLoginUser = mockLoginUser;
    }

    public Map <String, LoginUser> getLoginUserMap ()
    {
        return loginUserMap;
    }

    public Map<RequestMatcher, Collection<ConfigAttribute>> getSecurityResourceMap(boolean isRetrieveLatest)
    {
        if(securityResourceMap.isEmpty() || isRetrieveLatest) 
        {
            loadAllResource ();
        }
        return securityResourceMap;
    }

    public void setUserDao(UserDao userDao)
    {
        this.userDao = userDao;
    }

    public void setRoleDao(RoleDao roleDao)
    {
        this.roleDao = roleDao;
    }
    public void setAuthorityDao(AuthorityDao authorityDao)
    {
        this.authorityDao = authorityDao;
    }

    public boolean isInitialized()
    {
        return initialized;
    }

    public void setInitialized(boolean initialized)
    {
        this.initialized = initialized;
    }

    public List<AuthorityBean> getControllerRequestMappings()
    {
        return controllerRequestMappings;
    }

    public void setControllerRequestMappings(List<AuthorityBean> controllerRequestMappings)
    {
        this.controllerRequestMappings = controllerRequestMappings;
    }

    private void loadAllResource ()
    {
        List <Role> roles = (List <Role>) roleDao.findAll ();
        /*
         * 应当是资源为key， 权限为value。 资源通常为url(.do/.html)， 权限就是角色能访问的资源。
         * 一个资源可以由多个权限来访问。
         */
        securityResourceMap = new HashMap <RequestMatcher, Collection <ConfigAttribute>> ();
        for (Role role : roles)
        {
            ConfigAttribute configAttribute = new SecurityConfig (role.getName ());
            List<RoleAuthority> roleAuthorities = userDao.findRoleAuthorityByRole (role.getName ());
            for(RoleAuthority ra : roleAuthorities) 
            {
                RequestMatcher requestMatcher = new AntPathRequestMatcher (ra.getAuthorityValue ());
                
                /*
                 * 判断资源和权限的对应关系，如果已经存在相关的资源url，则要通过该url为key提取出权限集合， 将权限增加到权限集合中。
                 */
                if (securityResourceMap.containsKey (requestMatcher))
                {
                    Collection <ConfigAttribute> atts = securityResourceMap.get (requestMatcher);
                    atts.add (configAttribute);
                    securityResourceMap.put (requestMatcher, atts);
                }
                else
                {
                    Collection <ConfigAttribute> atts = new ArrayList <ConfigAttribute> ();
                    atts.add (configAttribute);
                    securityResourceMap.put (requestMatcher, atts);
                }
            }
        }
    }
    
    public void initUserRoleAuthority() 
    {
        if(this.initialized) 
        {
            try 
            {
                List <Authority> authorities = new ArrayList<Authority>();
                List <Authority> dbAuthorities = new ArrayList <Authority> ();
                Authority authority = null;
                LOGGER.info("controllerRequestMappings size: " + this.controllerRequestMappings.size());
                for(AuthorityBean authorityUrl : this.controllerRequestMappings) 
                {
                    if(null == authorityDao.findByName(authorityUrl.getName())) 
                    {
                        authority = new Authority(authorityUrl.getName());
                        authority.setUrl(authorityUrl.getUrl());
                        authority.setSystem(true);
                        authority.setEnabled(true);
                        Authority parentAuthority = authorityDao.save (authority);
                        dbAuthorities.add (parentAuthority);
                        authorities = new ArrayList<Authority>();
                        if(!CollectionUtils.isEmpty (authorityUrl.getChildren ())) 
                        {
                            for(AuthorityBean childAuthorityUrl : authorityUrl.getChildren ()) 
                            {
                                authority = new Authority(childAuthorityUrl.getName());
                                authority.setUrl(childAuthorityUrl.getUrl());
                                authority.setSystem(true);
                                authority.setEnabled(true);
                                authority.setParent (parentAuthority);
                                authorities.add(authority);
                            }
                            dbAuthorities.addAll((List <Authority>)authorityDao.save(authorities));
                        }
                    }
                }
                
                if(!CollectionUtils.isEmpty(dbAuthorities)) 
                {
                    String adminRole = PurchaseChargeProperties.getMockAdminRole();
                    if(null == roleDao.findByName(adminRole)) 
                    {
                        List<Role> roles = new ArrayList<Role>();
                        Role role = new Role(adminRole);
                        role.setAuthorities(dbAuthorities);
                        role.setSystem(true);
                        role.setEnabled(true);
                        roles.add(role);
                        List<Role> dbRoles = (List<Role>)roleDao.save(roles);
                        
                        if(!CollectionUtils.isEmpty(dbRoles)) 
                        {
                            String adminUser = PurchaseChargeProperties.getMockAdmin();
                            if(null == userDao.findByUserId(adminUser)) 
                            {
                                String adminPwd = PurchaseChargeProperties.getDefaultPassword();
                                User user = new User(adminUser, adminPwd);
                                user.setRoles(dbRoles);
                                user.setSystem(true);
                                user.setEnabled(true);
                                user.setFirstName(adminUser);
                                user.setLastName(adminUser);
                                userDao.save(user);
                            }
                        }
                    }
                }
            }
            catch (Exception e) 
            {
                LOGGER.warn("initUserRoleAuthority initialized failed", e);
            }
        }
    }
    
    @Override
    public void afterPropertiesSet () throws Exception
    {
        Assert.notNull (this.authorityDao, "a AuthorityDao is required");
        Assert.notNull (this.roleDao, "a roleDao is required");
        Assert.notNull (this.userDao, "a userDao is required");
        
        initUserRoleAuthority();
    }

}
